{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T08:51:43.526153Z",
     "start_time": "2020-04-03T08:51:43.522667Z"
    },
    "tags": [
     "parameters"
    ]
   },
   "outputs": [],
   "source": [
    "epochs = 10"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Teil 6 - Federated Learning auf MNIST mit einem CNN\n",
    "\n",
    "## Upgraden zu Federated Learning mit 10 Zeilen PyTorch + PySyft\n",
    "\n",
    "\n",
    "### Kontext \n",
    "\n",
    "Federated Learning ist eine spannende und aufstrebende Machine Learning Technik, die es Systemen ermöglicht auf dezentralisierten Daten zu lernen. Der Grundgedanke dabei ist die Daten an ihrem ursprünglichen Ort zu belassen und dadurch die Privatsphäre des Besitzers zu stärken. Dafür wird jedoch das Machine Learning Model an alle Daten-Besitzer verteilt. Eine direkte Anwendung dafür ist die Vorhersage des nächsten Wortes beim Tippen auf dem Smartphone: die Trainingsdaten - z. B. die gesendeten Nachrichten - sollen hierbei keinesfalls auf einem zentralen Server gesammelt werden. \n",
    "\n",
    "Die Verbreitung von Federated Learning ist stark an das Bewusstsein für Daten-Privatsphäre geknüpft. Die DSGVO der EU, welche seit Mai 2018 den Schutz der Daten vorschreibt, kann hierbei als Auslöser angesehen werden. Um einer Regulierung zuvorzukommen, haben große Unternehmen wie Apple oder Google begonnen, stark in diesen Bereich zu investieren und die Privatsphäre der eigenen Nutzer somit zu schützen. Jedoch stellen sie ihre Werkzeuge dafür nicht der Allgemeinheit zur Verfügung.  \n",
    "Bei OpenMined galuben wir daran, dass jeder sein Machine Learning Projekt einfach mit Werkzeugen zum Schutz der Privatsphäre ausstatten können sollte. Aus diesem Grund wurden z. B. [Werkzeuge zum Verschlüsseln der Daten](https://blog.openmined.org/training-cnns-using-spdz/) in einer einzigen Zeile von uns entworfen und nun veröffentlichen wir unser Federated Learning Gerüst, welches auf PyTorch 1.0 aufbaut, um ein intuitives Interface zum Bauen sicherer und skalierbarer Machine Learning Modele anzubieten. \n",
    "\n",
    "In diesem Tutorial wird direkt mit dem [Beispiel vom Trainieren eines CNN auf MNIST mit PyTorch](https://github.com/pytorch/examples/blob/master/mnist/main.py) gearbeitet. Es wird gezeigt wie einfach es ist dies mit der [PySyft Bibliothek](https://github.com/OpenMined/PySyft/) auf Federated Learning anzupassen. Dabei wird jeder Teil des Beispiels betrachtet und die angepassten Codezeilen hervorgehoben.\n",
    "\n",
    "Das Material steht auch auf diesem [Blogpost](https://blog.openmined.org/upgrade-to-federated-learning-in-10-lines) bereit.\n",
    "\n",
    "Autoren:\n",
    "- Théo Ryffel - GitHub: [@LaRiffle](https://github.com/LaRiffle)\n",
    "\n",
    "Übersetzer:\n",
    "- Jan Moritz Behnken - Github: [@JMBehnken](https://github.com/JMBehnken)\n",
    "\n",
    "**Ok, lassen Sie uns starten!**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Importe und Model Spezifikationen\n",
    "\n",
    "Zuerst erfolgen die offiziellen Importe."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T08:51:49.088484Z",
     "start_time": "2020-04-03T08:51:47.869658Z"
    }
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from torchvision import datasets, transforms"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Darauf folgen die PySyft spezifischen Importe. Auch werden die Helfer `alice` und `bob` erstellt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T08:51:53.300315Z",
     "start_time": "2020-04-03T08:51:49.831760Z"
    }
   },
   "outputs": [],
   "source": [
    "import syft as sy  # <-- NEW: import the Pysyft library\n",
    "hook = sy.TorchHook(torch)  # <-- NEW: hook PyTorch ie add extra functionalities to support Federated Learning\n",
    "bob = sy.VirtualWorker(hook, id=\"bob\")  # <-- NEW: define remote worker bob\n",
    "alice = sy.VirtualWorker(hook, id=\"alice\")  # <-- NEW: and alice"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Die Einstellungen für das Lernen werden festgelegt."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T08:59:29.303829Z",
     "start_time": "2020-04-03T08:59:29.258358Z"
    }
   },
   "outputs": [],
   "source": [
    "class Arguments():\n",
    "    def __init__(self):\n",
    "        self.batch_size = 64\n",
    "        self.test_batch_size = 1000\n",
    "        self.epochs = epochs\n",
    "        self.lr = 0.01\n",
    "        self.momentum = 0.5\n",
    "        self.no_cuda = False\n",
    "        self.seed = 1\n",
    "        self.log_interval = 30\n",
    "        self.save_model = False\n",
    "\n",
    "args = Arguments()\n",
    "\n",
    "use_cuda = not args.no_cuda and torch.cuda.is_available()\n",
    "\n",
    "torch.manual_seed(args.seed)\n",
    "\n",
    "device = torch.device(\"cuda\" if use_cuda else \"cpu\")\n",
    "\n",
    "kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Laden der Daten und verteilen an die Helfer\n",
    "\n",
    "Zuerst wird der Trainingsdatensatz vorverarbeitet und dann als Federated Datensatz auf alle Helfer mit der `.federate` Methode verteilt. Dieser gebündelte Datensatz wird einem Federated DataLoader übergeben. Der Test-Datensatz bleibt unangetastet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T09:03:06.998635Z",
     "start_time": "2020-04-03T09:02:38.606564Z"
    }
   },
   "outputs": [],
   "source": [
    "federated_train_loader = sy.FederatedDataLoader( # <-- this is now a FederatedDataLoader \n",
    "    datasets.MNIST('../data', train=True, download=True,\n",
    "                   transform=transforms.Compose([\n",
    "                       transforms.ToTensor(),\n",
    "                       transforms.Normalize((0.1307,), (0.3081,))\n",
    "                   ]))\n",
    "    .federate((bob, alice)), # <-- NEW: we distribute the dataset across all the workers, it's now a FederatedDataset\n",
    "    batch_size=args.batch_size, shuffle=True, **kwargs)\n",
    "\n",
    "test_loader = torch.utils.data.DataLoader(\n",
    "    datasets.MNIST('../data', train=False, transform=transforms.Compose([\n",
    "                       transforms.ToTensor(),\n",
    "                       transforms.Normalize((0.1307,), (0.3081,))\n",
    "                   ])),\n",
    "    batch_size=args.test_batch_size, shuffle=True, **kwargs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### CNN Spezifikationen\n",
    "\n",
    "Hier wird exakt dasselbe CNN Model verwendet wie im offiziellen Beispiel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T09:03:16.120297Z",
     "start_time": "2020-04-03T09:03:16.090655Z"
    }
   },
   "outputs": [],
   "source": [
    "class Net(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.conv1 = nn.Conv2d(1, 20, 5, 1)\n",
    "        self.conv2 = nn.Conv2d(20, 50, 5, 1)\n",
    "        self.fc1 = nn.Linear(4*4*50, 500)\n",
    "        self.fc2 = nn.Linear(500, 10)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.conv1(x))\n",
    "        x = F.max_pool2d(x, 2, 2)\n",
    "        x = F.relu(self.conv2(x))\n",
    "        x = F.max_pool2d(x, 2, 2)\n",
    "        x = x.view(-1, 4*4*50)\n",
    "        x = F.relu(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return F.log_softmax(x, dim=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Definieren der Trainings und Test Funktionen\n",
    "\n",
    "Weil die Trainings-Daten auf `alice` und `bob` aufgeteilt sind, muss in der Trainings-Funktion auch das Model für jeden Trainings-Batch an den richtigen Helfer versendet werden. Dann werden alle Operationen automatisiert aus der Ferne gestartet. Dabei wird dieselbe Syntax verwendet wie beim lokalen Verwenden von PyTorch. Anschließend wird das verbesserte Model zurück geholt und auch das Ergebnis der Loss-Funktion kann betrachtet werden. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T09:08:26.563394Z",
     "start_time": "2020-04-03T09:08:26.546707Z"
    }
   },
   "outputs": [],
   "source": [
    "def train(args, model, device, federated_train_loader, optimizer, epoch):\n",
    "    model.train()\n",
    "    for batch_idx, (data, target) in enumerate(federated_train_loader): # <-- now it is a distributed dataset\n",
    "        model.send(data.location) # <-- NEW: send the model to the right location\n",
    "        data, target = data.to(device), target.to(device)\n",
    "        optimizer.zero_grad()\n",
    "        output = model(data)\n",
    "        loss = F.nll_loss(output, target)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        model.get() # <-- NEW: get the model back\n",
    "        if batch_idx % args.log_interval == 0:\n",
    "            loss = loss.get() # <-- NEW: get the loss back\n",
    "            print('Train Epoch: {} [{}/{} ({:.0f}%)]\\tLoss: {:.6f}'.format(\n",
    "                epoch, batch_idx * args.batch_size, len(federated_train_loader) * args.batch_size,\n",
    "                100. * batch_idx / len(federated_train_loader), loss.item()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Die Test-Funktion wird nicht abgeändert!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-04-03T09:08:42.015738Z",
     "start_time": "2020-04-03T09:08:41.999269Z"
    }
   },
   "outputs": [],
   "source": [
    "def test(args, model, device, test_loader):\n",
    "    model.eval()\n",
    "    test_loss = 0\n",
    "    correct = 0\n",
    "    with torch.no_grad():\n",
    "        for data, target in test_loader:\n",
    "            data, target = data.to(device), target.to(device)\n",
    "            output = model(data)\n",
    "            test_loss += F.nll_loss(output, target, reduction='sum').item() # sum up batch loss\n",
    "            pred = output.argmax(1, keepdim=True) # get the index of the max log-probability \n",
    "            correct += pred.eq(target.view_as(pred)).sum().item()\n",
    "\n",
    "    test_loss /= len(test_loader.dataset)\n",
    "\n",
    "    print('\\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\\n'.format(\n",
    "        test_loss, correct, len(test_loader.dataset),\n",
    "        100. * correct / len(test_loader.dataset)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Starten des Trainings!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "start_time": "2020-04-03T09:09:00.470Z"
    }
   },
   "outputs": [],
   "source": [
    "%%time\n",
    "model = Net().to(device)\n",
    "optimizer = optim.SGD(model.parameters(), lr=args.lr) # TODO momentum is not supported at the moment\n",
    "\n",
    "for epoch in range(1, args.epochs + 1):\n",
    "    train(args, model, device, federated_train_loader, optimizer, epoch)\n",
    "    test(args, model, device, test_loader)\n",
    "\n",
    "if (args.save_model):\n",
    "    torch.save(model.state_dict(), \"mnist_cnn.pt\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Et voilà! Schon wurde ein Model mit Federated Learning auf fernen Daten trainiert!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Letzte Anmerkungen\n",
    "\n",
    "Eine wichtige Frage bleibt zu klären: **Wie lange dauert Federated Learning verglichen mit normalem PyTorch?**\n",
    "\n",
    "Die reine Rechenzeit dauert **etwas weniger als doppelt so lange** wie es mit normalem PyTorch dauern würde. Genau gemessen, dauert es 1.9 mal so lange, was im Hinblick auf die hinzugefügten Eigenschaften recht wenig ist."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### PySyft auf GitHub einen Stern geben! \n",
    "\n",
    "Der einfachste Weg, unserer Community zu helfen, besteht darin, die GitHub-Repos mit Sternen auszuzeichnen! Dies hilft, das Bewusstsein für die coolen Tools zu schärfen, die wir bauen. \n",
    "\n",
    "- [Gib PySyft einen Stern](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### Nutze unsere Tutorials auf GitHub!\n",
    "\n",
    "Wir haben hilfreiche Tutorials erstellt, um ein Verständnis für Federated und Privacy-Preserving Learning zu entwickeln und zu zeigen wie wir die einzelnen Bausteine weiter entwickeln.\n",
    "\n",
    "- [PySyft Tutorials ansehen](https://github.com/OpenMined/PySyft/tree/master/examples/tutorials)\n",
    "\n",
    "\n",
    "### Mach mit bei Slack! \n",
    "\n",
    "Der beste Weg, um über die neuesten Entwicklungen auf dem Laufenden zu bleiben, ist, sich unserer Community anzuschließen! Sie können dies tun, indem Sie das Formular unter [http://slack.openmined.org](http://slack.openmined.org) ausfüllen.\n",
    "\n",
    "### Treten Sie einem Code-Projekt bei! \n",
    "\n",
    "Der beste Weg, um zu unserer Community beizutragen, besteht darin, Entwickler zu werden! Sie können jederzeit zur PySyft GitHub Issues-Seite gehen und nach \"Projects\" filtern. Dies zeigt Ihnen alle Top-Level-Tickets und gibt einen Überblick darüber, an welchen Projekten Sie teilnehmen können! Wenn Sie nicht an einem Projekt teilnehmen möchten, aber ein wenig programmieren möchten, können Sie auch nach weiteren \"einmaligen\" Miniprojekten suchen, indem Sie nach GitHub-Problemen suchen, die als \"good first issue\" gekennzeichnet sind. \n",
    "\n",
    "- [PySyft Projects](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3AProject)\n",
    "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### Spenden\n",
    "\n",
    "Wenn Sie keine Zeit haben, zu unserer Codebase beizutragen, aber dennoch Unterstützung leisten möchten, können Sie auch Unterstützer unseres Open Collective werden. Alle Spenden fließen in unser Webhosting und andere Community-Ausgaben wie Hackathons und Meetups! \n",
    "\n",
    " - [OpenMined's Open Collective Page](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
